﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cloo;
using Processing;
using System.Threading;
using System.IO;
using Processing.Structures;
using General;

namespace KernelTests
{
    class Program
    {
        const string LOGFILE = "log.log";

        static Random random = new Random();
        static Processor processor;
        static FileStream fs;
        static StreamWriter sw;

        static void Main(string[] args)
        {
            try
            {
                Console.Title = "Kerneltest";
                Console.ForegroundColor = ConsoleColor.White;
                TimeClock.Start("Gesamtlaufzeit");


                if (File.Exists(LOGFILE)) File.Delete(LOGFILE);
                fs = new FileStream(LOGFILE, FileMode.OpenOrCreate, FileAccess.Write);
                sw = new StreamWriter(fs);
                Thread.Sleep(500);

                Log("--------------------------------------------------------------------------------");
                Log("Systeminformationen:");
                TimeClock.Start("Plattforminformationen sammeln");
                Info();
                Log("Plattforminformationen sammeln hat " + TimeClock.GetTime("Plattforminformationen sammeln") + " Sekunden benötigt.");

                Console.ForegroundColor = ConsoleColor.White;
                Log("");
                Log("--------------------------------------------------------------------------------");
                Log("Beginne Tests:");

                Console.ForegroundColor = ConsoleColor.DarkGray;
                TimeClock.Start("Ungemessener Testlauf");
                Highlight("Ungemessener Testlauf, da erste Initialisierung signifikant länger dauert,");
                Highlight("da hier entsprechene OpenCL Ressourcen noch geladen werden müssen.");
                processor = new Processor(Cloo.ComputePlatform.Platforms[0], Cloo.ComputePlatform.Platforms[0].Devices[0]);
                Float16[] xdaten = new Float16[1024 * 1024];
                for (int i = 0; i < xdaten.Length; i++) xdaten[i] = new Float16(i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1);
                Buffer<Float16> xgpu_data = processor.AllocateBuffer(xdaten);
                KernelAdder xka = new KernelAdder(processor);
                xka.input = xgpu_data;
                xka.elements = xdaten.Length;
                xka.Run();
                xka.Dispose();
                xgpu_data.Dispose();
                processor.Dispose();
                Log("Ungemessener Testlauf hat " + TimeClock.GetTime("Ungemessener Testlauf") + " Sekunden benötigt.");

                Console.ForegroundColor = ConsoleColor.White;
                foreach (bool debugMode in new bool[] { false, true })
                {
                    Log("");
                    Log("--------------------------------------------");
                    Log("Beginne Test " + (debugMode ? "mit" : "ohne") + " Debugmodus:");
                    TimeClock.Start("Test " + (debugMode ? "mit" : "ohne") + " Debugmodus");
                    Log("");

                    try
                    {
                        Log("");

                        try
                        {
                            TimeClock.Start("Initialisierung");
                            processor = new Processor(Cloo.ComputePlatform.Platforms[0], Cloo.ComputePlatform.Platforms[0].Devices[0]);
                            processor.DebugMode = debugMode;
                            Log("Initialisierung hat " + TimeClock.GetTime("Initialisierung") + " Sekunden benötigt.");
                            TimeClock.Start("Datensatz auf CPU erstellen");
                            Float16[] daten = new Float16[1024 * 1024];
                            for (int i = 0; i < daten.Length; i++) daten[i] = new Float16(i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1, i, i + 1);
                            Log("Datensatz auf CPU erstellen hat " + TimeClock.GetTime("Datensatz auf CPU erstellen") + " Sekunden benötigt.");
                            TimeClock.Start("Datentransfer zu GPU");
                            Buffer<Float16> gpu_data = processor.AllocateBuffer(daten);
                            Log("Datentransfer zu GPU hat " + TimeClock.GetTime("Datentransfer zu GPU") + " Sekunden benötigt.");

                            Log("");

                            TimeClock.Start("1. Kernel");
                            TimeClock.Start("Initialisierung 1. Kernel");
                            KernelAdder ka = new KernelAdder(processor);
                            ka.elements = daten.Length;
                            ka.input = gpu_data;
                            Log("Initialisierung 1. Kernel hat " + TimeClock.GetTime("Initialisierung 1. Kernel") + " Sekunden benötigt.");
                            Console.ForegroundColor = ConsoleColor.Gray;
                            TimeClock.Start("   Ausführung");
                            try { ka.Run(); }
                            catch (Exception e) { WriteException(e); }
                            Log("   Ausführung hat " + TimeClock.GetTime("   Ausführung") + " Sekunden benötigt.");
                            Console.ForegroundColor = ConsoleColor.White;
                            Log("1. Kernel hat " + TimeClock.GetTime("1. Kernel") + " Sekunden benötigt.");

                            Log("");

                            TimeClock.Start("2. Kernel");
                            TimeClock.Start("Initialisierung 2. Kernel");
                            KernelAdder kb = new KernelAdder(processor);
                            kb.elements = daten.Length;
                            kb.input = ka.output;
                            Log("Initialisierung 2. Kernel hat " + TimeClock.GetTime("Initialisierung 2. Kernel") + " Sekunden benötigt.");
                            Highlight("    Diese Initialisierung müsste bedeutend schneller sein.");
                            Console.ForegroundColor = ConsoleColor.Gray;
                            TimeClock.Start("   Ausführung");
                            try { kb.Run(); }
                            catch (Exception e) { WriteException(e); }
                            Log("   Ausführung hat " + TimeClock.GetTime("   Ausführung") + " Sekunden benötigt.");
                            Console.ForegroundColor = ConsoleColor.White;
                            Log("2. Kernel hat " + TimeClock.GetTime("2. Kernel") + " Sekunden benötigt.");

                            Log("");

                            Console.ForegroundColor = ConsoleColor.White;
                            Log("Korrektheitscheck:");
                            TimeClock.Start("Korrektheitscheck");
                            Console.ForegroundColor = ConsoleColor.Gray;
                            Highlight("    Stichprobe wird entnommen.");
                            try
                            {
                                int fehler = 0;
                                Log("\tK1\tK2\t\tK1\tK2");
                                TimeClock.Start("Gegenüberstellen der Ergebnisse");
                                for (int i = 0; i < 8; i++)
                                {
                                    Log("\t" + ka.cpuout[i * 1024].s0 + "\t" + kb.cpuout[i * 1024].s0
                                        + "\t\t" + ka.cpuout[(i + 8) * 1024].s0 + "\t" + kb.cpuout[(i + 8) * 1024].s0);
                                    fehler += ka.cpuout[i * 1024].s0 + 1 == kb.cpuout[i * 1024].s0 ? 0 : 1;
                                    fehler += ka.cpuout[(i + 8) * 1024].s0 + 1 == kb.cpuout[(i + 8) * 1024].s0 ? 0 : 1;
                                }
                                Log("Gegenüberstellen der Ergebnisse hat " + TimeClock.GetTime("Gegenüberstellen der Ergebnisse") + " Sekunden benötigt.");
                                Log(fehler == 0 ? "Keine Fehler in Stichprobe gefunden." : fehler + " Fehler in Stichprobe gefunden!");
                            }
                            catch (Exception e) { WriteException(e); }
                            Log("Korrektheitscheck hat " + TimeClock.GetTime("Korrektheitscheck") + " Sekunden benötigt.");
                            Console.ForegroundColor = ConsoleColor.White;
                        }
                        catch (Exception e) { WriteException(e); }
                    }
                    catch (Exception e) { WriteException(e); }
                                        Log("Test " + (debugMode ? "mit" : "ohne") + " Debugmodus hat " + TimeClock.GetTime("Test " + (debugMode ? "mit" : "ohne") + " Debugmodus") + " Sekunden benötigt.");
                    sw.Flush();
                }
                Log("--------------------------------------------------------------------------------");
                Log("Beginne Speichertests:");
                processor = new Processor(Cloo.ComputePlatform.Platforms[0], Cloo.ComputePlatform.Platforms[0].Devices[0]);

                foreach (long amount in new long[] { 1 * 1024 * 1024, 2 * 1024 * 1024, 5 * 1024 * 1024, 10 * 1024 * 1024, 20 * 1024 * 1024, 50 * 1024 * 1024, 100 * 1024 * 1024 })
                {
                    Dictionary<int, float> timesW = new Dictionary<int, float>();
                    Dictionary<int, float> timesR = new Dictionary<int, float>();
                    float wsum = 0;
                    float rsum = 0;
                    Console.ForegroundColor = ConsoleColor.White;
                    Log("");
                    Log("--------------------------------------------");
                    Log("Beginne Test mit " + (amount / 1024 / 1024) + " MB:");

                    float[] daten = new float[amount / 4];
                    for (int a = 0; a < amount / 4; a++) daten[a] = random.Next(0, a);

                    Console.ForegroundColor = ConsoleColor.Gray;
                    Log("");
                    Log("\tRead\tWrite");
                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    for (int d = 0; d < 10; d++)
                    {
                        try
                        {
                            TimeClock.Start((amount / 1024 / 1024) + "MB TestW " + d);
                            Buffer<float> gpu_daten = processor.AllocateBuffer(daten);
                            timesW.Add(d, (float)TimeClock.GetTime((amount / 1024 / 1024) + "MB TestW " + d));

                            KernelNix kn = new KernelNix(processor);
                            kn.input = gpu_daten;
                            kn.Run();
                            Thread.Sleep((int)Math.Sqrt(amount / 1024 / 1024) * 100);

                            TimeClock.Start((amount / 1024 / 1024) + "MB TestR " + d);
                            if (kn.output.Data == null) Console.WriteLine("Lesen fehlgeschlagen.");
                            timesR.Add(d, (float)TimeClock.GetTime((amount / 1024 / 1024) + "MB TestR " + d));

                            Log("\t" + Math.Round(timesR[d] * 1000, 0) + "\t" + Math.Round(timesW[d] * 1000, 0) + "");
                            wsum += timesW[d];
                            rsum += timesR[d];

                            gpu_daten.Dispose();
                            kn.output.Dispose();
                        }
                        catch
                        {
                            Log("\t--- fail ---");
                        }
                    }
                    Log("");
                    Console.ForegroundColor = ConsoleColor.Gray;
                    Log("Lesedauer im Schnitt: " + 1000 * rsum / timesR.Count + " ms");
                    Log("Schreibedauer im Schnitt: " + 1000 * wsum / timesW.Count + " ms");
                    Log("Geschätzte Bandbreite (lesen): " + ((amount / 1024 / 1024) / (rsum / timesR.Count)) + " MB/s");
                    Log("Geschätzte Bandbreite (schreiben): " + ((amount / 1024 / 1024) / (wsum / timesW.Count)) + " MB/s");
                    sw.Flush();
                }
                processor.Dispose();

                Log("--------------------------------------------------------------------------------");
                Log("");
                Log("Gesamtlaufzeit hat " + TimeClock.GetTime("Gesamtlaufzeit") + " Sekunden benötigt.");
                sw.Flush();
                fs.Close();
                Console.WriteLine("--- Beliebige Taste drücken zum Beenden ---");
                Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        private static void Highlight(string s)
        {
            ConsoleColor old = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Log(s);
            Console.ForegroundColor = old;
        }

        private static void WriteException(Exception e)
        {
            ConsoleColor old = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            Log("Exception:" + e.Message);
            Console.ForegroundColor = old;
        }

        private static void Info()
        {
            ComputePlatform plattform = ComputePlatform.Platforms[0];
            ComputeDevice device = plattform.Devices[0];

            Console.ForegroundColor = ConsoleColor.White;
            Log("System");
            Console.ForegroundColor = ConsoleColor.Gray;

            Log(" OpenCL Version: " + device.Version);
            Log(" Plattform:   " + plattform.Name);

            Log("");
            Console.ForegroundColor = ConsoleColor.White;
            Log("Device");
            Console.ForegroundColor = ConsoleColor.Gray;

            Log(" Device:  " + device.Name);
            Log(" Type: " + device.Type.ToString());
            Log(" Driverversion: " + device.DriverVersion);
            Log(" Available: " + device.Available);

            Log("");
            Console.ForegroundColor = ConsoleColor.White;
            Log("Processors");
            Console.ForegroundColor = ConsoleColor.Gray;

            Log(" Compute Units: " + device.MaxComputeUnits + " streaming multiprocessors");
            Log(" Frequency: " + device.MaxClockFrequency + " MHz");
            Log(" Max Workitems: " + device.MaxWorkGroupSize + " streaming processors (" + (device.MaxWorkGroupSize / device.MaxComputeUnits) + " each SM)");
            Log(" Max Dimensions: " + device.MaxWorkItemDimensions);

            Log("");
            Console.ForegroundColor = ConsoleColor.White;
            Log("Memory");
            Console.ForegroundColor = ConsoleColor.Gray;

            Log(" Memory: " + device.GlobalMemorySize + " bytes (" + (device.GlobalMemorySize / 1024 / 1024) + " mb)");
            Log(" GCache: " + device.GlobalMemoryCacheSize + " bytes");
            Log(" CCache: " + device.MaxConstantBufferSize + " bytes");
            Log(" Register: " + device.LocalMemorySize + " bytes");

            Log("");
            Console.ForegroundColor = ConsoleColor.White;
            Log("Adressing");
            Console.ForegroundColor = ConsoleColor.Gray;

            Log(" Adressing bits: " + device.AddressBits + " Bit");
            Log(" Endian: " + (device.EndianLittle ? "Little" : "Big"));
        }

        public static void Log(string s)
        {
            Console.WriteLine(s);
            sw.WriteLine(s);
        }
    }
}
